// Copyright (c) 2023 CHANGLEI. All rights reserved.

import 'dart:math' as math;

import 'package:flutter/gestures.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_grasp/src/widget/animated_draggable.dart';
import 'package:flutter_grasp/src/widget/animated_shifted_box_boundary.dart';
import 'package:flutter_grasp/src/widget/dragging_auto_scroller.dart';

/// 拖动排序回调
typedef SwappableCallback = void Function(int fromIndex, int toIndex);

/// 在未命中的时候，执行该回调
typedef SwappableWillNotAcceptCallback = void Function(int fromIndex, int toIndex);

/// 自定义拖动排序
typedef SwappableHandler = int Function(int fromIndex, int toIndex, bool dragging);

/// 构建拖动时的[child]
typedef SwappablePlaceholderBuilder = Widget Function(BuildContext context, int index, Widget child);

/// 拖动排序回调
typedef SwappableEffect = SwapEffect Function(int fromIndex, int toIndex);

/// [DragTarget.builder]
typedef TargetChildBuilder = Widget Function(
  BuildContext context,
  List<Data?> candidateData,
  List<dynamic> rejectedData,
  Widget child,
);

/// 交换方式
enum SwapEffect {
  /// 插入
  insert,

  /// 交换
  swap,
}

/// 拖动排序的数据
@immutable
final class Data {
  /// 拖动的数据
  const Data(this._state, this.index);

  final SwappableState _state;

  /// 对应的index
  final int index;

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Data && runtimeType == other.runtimeType && _state == other._state && index == other.index;

  @override
  int get hashCode => _state.hashCode ^ index.hashCode;

  @override
  String toString() {
    return 'Data{index: $index}';
  }
}

/// Created by changlei on 2020/8/12.
///
/// 拖动排序
final class Swappable extends StatefulWidget {
  /// 拖动排序
  const Swappable({
    super.key,
    required this.child,
    required this.itemCount,
    this.onSwapHandler,
    this.onSwapEffect,
    this.onWillNotAccept,
    this.onDragSwap,
    this.onDragStart,
    this.onDragStarted,
    this.onDragUpdate,
    this.onDraggableCanceled,
    this.onDragCompleted,
    this.onDragEnd,
    this.onDragEnded,
  }) : assert(itemCount >= 0);

  /// child
  final Widget child;

  /// 自定义排序逻辑
  final SwappableHandler? onSwapHandler;

  /// 排序效果
  final SwappableEffect? onSwapEffect;

  /// 在未命中的时候，执行该回调，注意，此方法只在拖动的时候执行，手动调用[SwappableState.swap]的时候，不执行
  final SwappableWillNotAcceptCallback? onWillNotAccept;

  /// 触发排序
  final SwappableCallback? onDragSwap;

  /// item的数量
  final int itemCount;

  /// 拖动即将开始，即按下的时候
  final ValueChanged<Data>? onDragStart;

  /// Called when the draggable starts being dragged.
  final ValueChanged<Data>? onDragStarted;

  /// Called when the draggable is dragged.
  ///
  /// This function will only be called while this widget is still mounted to
  /// the tree (i.e. [State.mounted] is true), and if this widget has actually moved.
  final ValueChanged<Data>? onDragUpdate;

  /// Called when the draggable is dropped without being accepted by a [DragTarget].
  ///
  /// This function might be called after this widget has been removed from the
  /// tree. For example, if a drag was in progress when this widget was removed
  /// from the tree and the drag ended up being canceled, this callback will
  /// still be called. For this reason, implementations of this callback might
  /// need to check [State.mounted] to check whether the state receiving the
  /// callback is still in the tree.
  final ValueChanged<Data>? onDraggableCanceled;

  /// Called when the draggable is dropped and accepted by a [DragTarget].
  ///
  /// This function might be called after this widget has been removed from the
  /// tree. For example, if a drag was in progress when this widget was removed
  /// from the tree and the drag ended up completing, this callback will
  /// still be called. For this reason, implementations of this callback might
  /// need to check [State.mounted] to check whether the state receiving the
  /// callback is still in the tree.
  final ValueChanged<Data>? onDragCompleted;

  /// Called when the draggable is dropped.
  ///
  /// The velocity and offset at which the pointer was moving when it was
  /// dropped is available in the [DraggableDetails]. Also included in the
  /// `details` is whether the draggable's [DragTarget] accepted it.
  ///
  /// This function will only be called while this widget is still mounted to
  /// the tree (i.e. [State.mounted] is true).
  final ValueChanged<Data>? onDragEnd;

  /// 拖动结束切动画一并结束了
  final ValueChanged<Data>? onDragEnded;

  @override
  State<Swappable> createState() => SwappableState();
}

/// 拖动排序state
final class SwappableState extends State<Swappable> {
  final List<GlobalKey<State<StatefulWidget>>> _itemKeys = <GlobalKey>[];

  int? _willAcceptIndex;

  @override
  void initState() {
    _itemKeys.addAll(List<GlobalKey>.generate(widget.itemCount, _createKey));
    super.initState();
  }

  @override
  void didUpdateWidget(Swappable oldWidget) {
    if (widget.itemCount > oldWidget.itemCount) {
      final delta = widget.itemCount - oldWidget.itemCount;
      _itemKeys.addAll(List<GlobalKey>.generate(delta, _createKey));
    } else if (widget.itemCount < oldWidget.itemCount) {
      _itemKeys.removeRange(widget.itemCount, math.min(oldWidget.itemCount, _itemKeys.length));
    }
    super.didUpdateWidget(oldWidget);
  }

  GlobalKey _createKey(int index) => GlobalKey(debugLabel: index.toString());

  /// 添加一个item
  void add({int? index}) {
    assert(index == null || (index >= 0 && index <= _itemKeys.length));
    if (index == null) {
      _itemKeys.add(_createKey(_itemKeys.length));
    } else {
      _itemKeys.insert(index, _createKey(index));
    }
  }

  /// 删除一个item
  void remove({int? index}) {
    assert(index == null || (index >= 0 && index < _itemKeys.length));
    if (index == null) {
      _itemKeys.removeLast();
    } else {
      _itemKeys.removeAt(index);
    }
  }

  /// 主动排序，尽量不要使用这个方法，因为他没有经过严格的测试，目前在测试阶段，是提供给[SwappableGroup]使用的
  int swap(int fromIndex, int toIndex, {bool dragging = true, bool feedback = true}) {
    if (fromIndex == toIndex) {
      return toIndex;
    }
    final willAcceptIndex = _onSwap(fromIndex, toIndex, dragging);
    if (dragging) {
      _willAcceptIndex = willAcceptIndex;
    }
    if (feedback) {
      widget.onDragSwap?.call(fromIndex, toIndex);
    }
    return willAcceptIndex;
  }

  int _onSwap(int fromIndex, int toIndex, [bool dragging = true]) {
    toIndex = widget.onSwapHandler?.call(fromIndex, toIndex, dragging) ?? toIndex;
    final maxIndex = _itemKeys.length - 1;
    final validFromIndex = fromIndex.clamp(0, maxIndex);
    final validToIndex = toIndex.clamp(0, maxIndex);
    if (validFromIndex != validToIndex) {
      final effect = widget.onSwapEffect?.call(fromIndex, validToIndex);
      if (effect == SwapEffect.swap) {
        final fromKey = _itemKeys.elementAt(fromIndex);
        _itemKeys[fromIndex] = _itemKeys.elementAt(validToIndex);
        _itemKeys[validToIndex] = fromKey;
      } else {
        _itemKeys.insert(validToIndex, _itemKeys.removeAt(validFromIndex));
      }
    }
    return validToIndex;
  }

  bool _onWillAcceptWithDetails(DragTargetDetails<Data> details, int index) {
    final data = details.data;
    if (data._state != this) {
      // 不是当前控件
      return false;
    }
    final fromIndex = _willAcceptIndex ?? data.index;
    bool accept = fromIndex != index;
    if (accept) {
      final toIndex = _onSwap(fromIndex, index);
      if (fromIndex != toIndex) {
        _willAcceptIndex = toIndex;
        widget.onDragSwap?.call(fromIndex, index);
      } else {
        accept = false;
        widget.onWillNotAccept?.call(fromIndex, index);
      }
    }
    return accept;
  }

  void _onDragStart(Data data) {
    widget.onDragStart?.call(data);
  }

  void _onDragStarted(Data data) {
    widget.onDragStarted?.call(data);
  }

  void _onDragUpdate(Data data) {
    widget.onDragUpdate?.call(data);
  }

  void _onDragEnd(Data data) {
    _willAcceptIndex = null;
    widget.onDragEnd?.call(data);
  }

  void _onDragEnded(Data data) {
    widget.onDragEnded?.call(data);
  }

  void _onDraggableCanceled(Data data) {
    _disposeWillAcceptIndexIfChildInactive();
    widget.onDraggableCanceled?.call(data);
  }

  void _onDragCompleted(Data data) {
    _disposeWillAcceptIndexIfChildInactive();
    widget.onDragCompleted?.call(data);
  }

  void _disposeWillAcceptIndexIfChildInactive() {
    final index = _willAcceptIndex;
    if (index == null || index >= _itemKeys.length) {
      return;
    }
    if (_itemKeys[index].currentContext?.mounted != true) {
      _willAcceptIndex = null;
    }
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedShiftedBoxBoundary(
      child: widget.child,
    );
  }
}

/// 拖动排序
final class SliverSwappable extends Swappable {
  /// 拖动排序
  const SliverSwappable({
    super.key,
    required super.child,
    required super.itemCount,
    super.onSwapHandler,
    super.onSwapEffect,
    super.onWillNotAccept,
    super.onDragSwap,
    super.onDragStart,
    super.onDragStarted,
    super.onDragUpdate,
    super.onDraggableCanceled,
    super.onDragCompleted,
    super.onDragEnd,
    super.onDragEnded,
  });

  @override
  SwappableState createState() => SliverSwappableState();
}

/// 拖动排序state
final class SliverSwappableState extends SwappableState {
  @override
  Widget build(BuildContext context) {
    return SliverAnimatedShiftedBoxBoundary(
      sliver: widget.child,
    );
  }
}

/// [Swappable] item
class SwappableChild extends StatelessWidget {
  /// [Swappable] item
  const SwappableChild({
    super.key,
    required this.index,
    this.feedbackBuilder,
    this.childWhenDraggingBuilder,
    this.dragTargetBuilder,
    this.duration = const Duration(milliseconds: 300),
    this.curve = Curves.linearToEaseOut,
    this.axis,
    this.autoScrollBoundary,
    this.ignoringFeedbackPointer = true,
    this.ignoringFeedbackSemantics = true,
    this.allowedButtonsFilter,
    this.alignment = Alignment.center,
    this.delay = kLongPressTimeout,
    this.dragAnchorStrategy = childDragAnchorStrategy,
    this.feedbackOffset = Offset.zero,
    this.hapticFeedbackOnStart = true,
    this.maxSimultaneousDrags,
    this.tickerOffset = true,
    required this.child,
  });

  /// 对应的索引
  final int index;

  /// 构建拖动的feedback
  final SwappablePlaceholderBuilder? feedbackBuilder;

  /// 构建拖动的child
  final SwappablePlaceholderBuilder? childWhenDraggingBuilder;

  /// 构建[DragTarget.builder]
  final TargetChildBuilder? dragTargetBuilder;

  /// The length of time this animation should last.
  final Duration duration;

  /// The curve to use in the forward direction.
  final Curve curve;

  /// The [Axis] to restrict this draggable's movement, if specified.
  ///
  /// When axis is set to [Axis.horizontal], this widget can only be dragged
  /// horizontally. Behavior is similar for [Axis.vertical].
  ///
  /// Defaults to allowing drag on both [Axis.horizontal] and [Axis.vertical].
  ///
  /// When null, allows drag on both [Axis.horizontal] and [Axis.vertical].
  ///
  /// For the direction of gestures this widget competes with to start a drag
  /// event, see [Draggable.affinity].
  final Axis? axis;

  /// 自动滚动边界
  final AutoScrollBoundary? autoScrollBoundary;

  /// Whether the [feedback] widget is ignored during hit testing.
  ///
  /// Regardless of whether this widget is ignored during hit testing, it will
  /// still consume space during layout and be visible during painting.
  ///
  /// Defaults to true.
  final bool ignoringFeedbackPointer;

  /// Whether the semantics of the [feedback] widget is ignored when building
  /// the semantics tree.
  ///
  /// This value should be set to false when the [feedback] widget is intended
  /// to be the same object as the [child].  Placing a [GlobalKey] on this
  /// widget will ensure semantic focus is kept on the element as it moves in
  /// and out of the feedback position.
  ///
  /// Defaults to true.
  final bool ignoringFeedbackSemantics;

  /// {@macro flutter.gestures.multidrag._allowedButtonsFilter}
  final AllowedButtonsFilter? allowedButtonsFilter;

  /// The alignment of the child within the parent when the parent is not yet
  /// the same size as the child.
  final AlignmentGeometry alignment;

  /// The duration that a user has to press down before a long press is registered.
  ///
  /// Defaults to [kLongPressTimeout].
  final Duration delay;

  /// A strategy that is used by this draggable to get the anchor offset when it
  /// is dragged.
  ///
  /// The anchor offset refers to the distance between the users' fingers and
  /// the [feedback] widget when this draggable is dragged.
  ///
  /// This property's value is a function that implements [DragAnchorStrategy].
  /// There are two built-in functions that can be used:
  ///
  ///  * [childDragAnchorStrategy], which displays the feedback anchored at the
  ///    position of the original child.
  ///
  ///  * [pointerDragAnchorStrategy], which displays the feedback anchored at the
  ///    position of the touch that started the drag.
  final DragAnchorStrategy dragAnchorStrategy;

  /// The feedbackOffset can be used to set the hit test target point for the
  /// purposes of finding a drag target. It is especially useful if the feedback
  /// is transformed compared to the child.
  final Offset feedbackOffset;

  /// Whether haptic feedback should be triggered on drag start.
  final bool hapticFeedbackOnStart;

  /// How many simultaneous drags to support.
  ///
  /// When null, no limit is applied. Set this to 1 if you want to only allow
  /// the drag source to have one item dragged at a time. Set this to 0 if you
  /// want to prevent the draggable from actually being dragged.
  ///
  /// If you set this property to 1, consider supplying an "empty" widget for
  /// [childWhenDragging] to create the illusion of actually moving [child].
  final int? maxSimultaneousDrags;

  /// 是否响应offset动画
  final bool tickerOffset;

  /// child
  final Widget child;

  @override
  Widget build(BuildContext context) {
    final state = context.findAncestorStateOfType<SwappableState>()!;
    assert(index >= 0 && state._itemKeys.length > index);
    final data = Data(state, index);
    Widget? feedback;
    if (feedbackBuilder case final builder?) {
      feedback = Builder(
        builder: (context) {
          return builder(context, index, child);
        },
      );
    }
    Widget? childWhenDragging;
    if (childWhenDraggingBuilder case final builder?) {
      childWhenDragging = Builder(
        builder: (context) {
          return builder(context, index, child);
        },
      );
    }
    return KeyedSubtree(
      key: state._itemKeys[index],
      child: AnimatedLongPressDraggable<Data>(
        data: data,
        onDragStart: (details) => state._onDragStart(data),
        onDragStarted: () => state._onDragStarted(data),
        onDragUpdate: (details) => state._onDragUpdate(data),
        onDragEnd: (details) => state._onDragEnd(data),
        onDragEnded: (details) => state._onDragEnded(data),
        onDraggableCanceled: (velocity, offset) => state._onDraggableCanceled(data),
        onDragCompleted: () => state._onDragCompleted(data),
        feedback: feedback,
        childWhenDragging: childWhenDragging,
        duration: duration,
        curve: curve,
        axis: axis,
        autoScrollBoundary: autoScrollBoundary,
        ignoringFeedbackPointer: ignoringFeedbackPointer,
        ignoringFeedbackSemantics: ignoringFeedbackSemantics,
        allowedButtonsFilter: allowedButtonsFilter,
        alignment: alignment,
        delay: delay,
        dragAnchorStrategy: dragAnchorStrategy,
        feedbackOffset: feedbackOffset,
        hapticFeedbackOnStart: hapticFeedbackOnStart,
        maxSimultaneousDrags: maxSimultaneousDrags,
        tickerOffset: tickerOffset,
        child: DragTarget<Data>(
          onWillAcceptWithDetails: (details) => state._onWillAcceptWithDetails(details, index),
          builder: (context, candidateData, rejectedData) {
            return dragTargetBuilder?.call(context, candidateData, rejectedData, child) ?? child;
          },
        ),
      ),
    );
  }
}
